The Membership Provider
The
beauty of the membership model lies not merely in the extremely compact
code you need to write to validate or manage users but also in the fact
that the model is abstract and extensible. For example, if you have an
existing data store filled with user information, you can integrate it
with the membership API without much effort. All you have to do is
write a custom data provider—that is, a class that inherits from MembershipProvider which, in turn, inherits from ProviderBase:
public class MyAppMembershipProvider : MembershipProvider
{
// Implements all abstract members of the class and, if
// needed, defines custom functionality
...
}
This
approach can be successfully employed to migrate existing
authentication code to newer versions of ASP.NET applications and,
perhaps more importantly, to link a custom and existing data store to
the membership API. We’ll return to this subject in a moment.
The ProviderBase Class
All the providers used in ASP.NET—not just membership providers—implement a common set of members: the members defined by the ProviderBase class. The class comes with one method, Initialize, and one property, Name. The Name property returns the official name of the provider class. The Initialize
method takes the name of the provider and a name/value collection
object packed with the content of the provider’s configuration section.
The method is supposed to initialize its internal state with the values
just read out of the web.config file.
The MembershipProvider Class
Many of the methods and properties used with the Membership
class are actually implemented by calling into a corresponding method
or property in the underlying provider. It comes as no surprise then
that many of the methods listed in Table 17-10, which are defined by the MembershipProvider base class, support the functions you saw in Table 17-9 that are implemented by the dependent Membership class. However, note that Table 1 and Table 2 are very similar but not identical.
Table 2. Methods of the MembershipProvider Class
Method | Description |
---|
ChangePassword | Takes a username in addition to the old and new password and changes the user’s password. |
ChangePasswordQuestionAndAnswer | Takes a username and password and changes the pair of question/answer challenges that allows reading and changing the password. |
CreateUser | Creates a new user account, and returns a MembershipUser-derived class. The method takes the username, password, and e-mail address. |
DeleteUser | Deletes the record that corresponds to the specified username. |
FindUsersByEmail | Returns a collection of membership users whose e-mail address corresponds to the specified e-mail. |
FindUsersByName | Returns a collection of membership users whose username matches the specified username. |
GetAllUsers | Returns the collection of all users managed by the provider. |
GetNumberOfUsersOnline | Returns the number of users that are currently considered to be on line. |
GetPassword | Takes the username and the password’s answer and returns the current password for the user. |
GetUser | Returns the information available about the specified username. |
GetUserNameByEmail | Takes an e-mail address, and returns the corresponding username. |
ResetPassword | Takes the username and the password’s answer, and resets the user password to an auto-generated password. |
UpdateUser | Updates the information available about the specified user. |
ValidateUser | Validates the specified credentials against the stored list of users. |
All these methods are marked as abstract virtual in the class (must-inherit, overridable in Visual Basic .NET jargon). The MembershipProvider class also features a few properties. They are listed in Table 3.
Table 3. Properties of the MembershipProvider Class
Property | Description |
---|
ApplicationName | Returns the provider’s nickname. |
EnablePasswordReset | Indicates whether the provider supports password reset. |
EnablePasswordRetrieval | Indicates whether the provider supports password retrieval. |
MaxInvalidPasswordAttempts | Returns the maximum number of invalid password attempts allowed before the user is locked out. |
MinRequiredNonAlphanumericCharacters | Returns the minimum number of punctuation characters required in the password. |
MinRequiredPasswordLength | Returns the minimum required length for a password. |
PasswordAttemptWindow | Returns the number of minutes in which a maximum number of invalid password attempts are allowed before the user is locked out. |
PasswordStrengthRegularExpression | Returns the regular expression that the password must comply with. |
RequiresQuestionAndAnswer | Indicates whether the provider requires a question/answer challenge to enable password changes. |
RequiresUniqueEmail | Indicates whether the provider is configured to require a unique e-mail address for each user name. |
Extending the Provider’s Interface
The provider can also store additional information with each user. For example, you can derive a custom class from MembershipUser, add any extra members, and return an instance of that class via the standard GetUser method of the membership API.
To use the new class, you cast the object returned by GetUser to the proper type, as shown here:
MyCompanyUser user = (MyCompanyUser) Membership.GetUser(name);
In addition to the members listed in Table 2 and Table 3,
a custom membership provider can add new methods and properties. These
are defined outside the official schema of the provider base class and
are therefore available only to applications aware of this custom
provider.
MyCompanyProvider prov = (MyCompanyProvider) Membership.Provider;
Note
The Providers collection property allows you to use a dynamically selected provider: MembershipProvider prov = Membership.Providers["ProviderName"];
This
feature allows applications to support multiple providers
simultaneously. For example, you can design your application to support
a legacy database of users through a custom provider, while storing new
users in a standard SQL Server 2005 table. In this case, you use
different membership providers for different users. |
A Custom Provider for ASP.NET 1.x Code
Earlier
in the chapter, we discussed a few sample pages using the Employees
table in the SQL Server 2000 Northwind database as the data store for
user accounts. Let’s turn this into a membership provider and register
it with the WSAT tool:
public class MyMembershipProvider : MembershipProvider
{
public MyMembershipProvider()
{
}
public override bool ChangePassword(string username,
string oldPassword, string newPassword)
{
// If you don't intend to support a given method
// just throw an exception
throw new NotSupportedException();
}
...
public override bool ValidateUser(string username, string password)
{
return AuthenticateUser(username, password);
}
private bool AuthenticateUser(string username, string pswd)
{
// Place here any analogous code you may have in your
// ASP.NET 1.x application
}
}
You define a new class derived from MembershipProvider. In this class definition, you have to override all the members in Table 17-10 and Table 17-11. If you don’t intend to support a given method or property, for that method just throw a NotSupportedException exception. For the methods you do plan to support—which for the previous example included only ValidateUser—you
write the supporting code. At this point, nothing prevents you from
reusing code from your old application. There are two key benefits with
this approach: you reuse most of your code (perhaps with a little bit
of refactoring), and your application now embraces the new membership
model of ASP.NET version 2.0 and later.
Generally
speaking, when writing providers there are three key issues to look at:
lifetime of the provider, thread-safety, and atomicity. The provider is
instantiated as soon as it proves necessary but only once per ASP.NET
application. This fact gives the provider the status of a stateful
component, but at the price of protecting that state from cross-thread
access. A provider is not thread-safe, and it will be your
responsibility to guarantee that any critical data is locked before
use. Finally, some functions in a provider can be made of multiple
steps. Developers are responsible for ensuring atomicity of the
operations either through database transactions (whenever possible) or
through locks. For more information, refer to http://msdn2.microsoft.com/en-us/library/aa479030.aspx.
Configuring a Membership Provider
You register a new provider through the <membership> section of the web.config file. The section contains a child <providers> element under which additional providers are configured:
<membership>
<providers>
<add name="MyMembershipProvider"
type="ProAspNet20.MyMembershipProvider" />
<providers>
</membership>
You can change the default provider through the defaultProvider attribute of the <membership> section. Figure 4 shows the new provider in WSAT.
With
the new provider in place, the code to verify credentials reduces to
the following code, which is the same as you saw earlier in the chapter:
void LogonUser(object sender, EventArgs e)
{
string user = userName.Text;
string pswd = passWord.Text;
if (Membership.ValidateUser(user, pswd))
FormsAuthentication.RedirectFromLoginPage(user, false);
else
errorMsg.Text = "Sorry, yours seems not to be a valid account.";
}
There’s
more than just this with the membership API. Now a login page has a
relatively standard structure and a relatively standard code attached.
At least in the simplest scenarios, it can be reduced to a composite
control with no binding code. This is exactly what security controls
do. Before we get to cover this new family of server controls, though,
let’s review roles and their provider-based management.